Machine Learning review and intro to tidymodels
Read through and follow along with the Machine Learning review with an intro to the tidymodels package posted on the Course Materials page.
Tasks:
- Read about the hotel booking data,
hotels, on the Tidy Tuesday page it came from. There is also a link to an article from the original authors. The outcome we will be predicting is called is_canceled.
Without doing any analysis, what are some variables you think might be predictive and why? The variables that stood out to me the most were previous_cancellations and previous_bookings_not_canceled. These give us a glimpse into their history and if they have canceled multiple times in the past then they might do it again. is_repeated_guest is also a possible indicator. Guests who have stayed in the hotel might be more reliable to uphold their reservation.
What are some problems that might exist with the data? You might think about how it was collected and who did the collecting. The data came from two hotels, which might not be representative of bookings made by other places offering accommodation.
If we construct a model, what type of conclusions will be able to draw from it? We might be able to determine predictor(s) for our response variable, is_canceled, and assess the significance.
- Create some exploratory plots or table summaries of the variables in the dataset. Be sure to also examine missing values or other interesting values.
Looking at numerical variables:
hotels %>%
select(-is_canceled, -is_repeated_guest) %>%
select(where(is.numeric)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_histogram(bins = 30) +
facet_wrap(vars(variable),
scales = "free")

and categorical:
hotels %>%
# these variables have many categories and showing them doesn't convey any information
select(-agent, -country, -company) %>%
mutate_if(is.character, as.factor) %>%
# doing this so that the plot displays the months in chronological order
mutate(arrival_date_month = factor(arrival_date_month, levels = month.name),
is_repeated_guest = factor(is_repeated_guest)) %>%
select(where(is.factor)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_bar() +
facet_wrap(vars(variable),
scales = "free",
nrow = 2) +
theme(axis.text.x = element_text(angle = 40, hjust = 1, size = 8.5))

The plots for agent, company, and country looked very messy because each has so many categories. Instead of plotting we can look at their table summaries:
hotels %>%
count(agent) %>%
arrange(desc(n)) %>%
head(5)
hotels %>%
count(company) %>%
arrange(desc(n)) %>%
head(5)
hotels %>%
count(country) %>%
arrange(desc(n)) %>%
head(5)
Checking for missing data:
hotels %>%
add_n_miss() %>%
count(n_miss_all)
Some observations I have are:
- There are lots of 0s in many numerical variables such as
adults, babies, booking_changes, etc.
- Average daily rate
adr seems to be very left-skewed.
- Many bookings have NULL for
agent and company.
- Most observations in the dataset are complete and the four incomplete ones are only missing one value each.
- First, we will do a couple things to get the data ready, including making the outcome a factor (needs to be that way for logistic regression), removing the year variable and some reservation status variables, and removing missing values (not NULLs but true missing values). Split the data into a training and test set, stratifying on the outcome variable,
is_canceled. Since we have a lot of data, we’re going to split the data 50/50 between training and test. I have already set.seed() for you. Be sure to use hotels_mod in the splitting.
hotels_mod <- hotels %>%
mutate(is_canceled = as.factor(is_canceled)) %>%
mutate(across(where(is.character), as.factor)) %>%
select(-arrival_date_year,
-reservation_status,
-reservation_status_date) %>%
add_n_miss() %>%
filter(n_miss_all == 0) %>%
select(-n_miss_all)
set.seed(494)
hotels_split <- initial_split(hotels_mod, prop = .5)
hotels_training <- training(hotels_split)
hotels_testing <- testing(hotels_split)
- In this next step, we are going to do the pre-processing. Usually, I won’t tell you exactly what to do here, but for your first exercise, I’ll tell you the steps.
- Set up the recipe with
is_canceled as the outcome and all other variables as predictors (HINT: ~.).
- Use a
step_XXX() function or functions (I think there are other ways to do this, but I found step_mutate_at() easiest) to create some indicator variables for the following variables: children, babies, and previous_cancellations. So, the new variable should be a 1 if the original is more than 0 and 0 otherwise. Make sure you do this in a way that accounts for values that may be larger than any we see in the dataset.
- For the
agent and company variables, make new indicator variables that are 1 if they have a value of NULL and 0 otherwise.
- Use
fct_lump_n() to lump together countries that aren’t in the top 5 most occurring.
- If you used new names for some of the new variables you created, then remove any variables that are no longer needed.
- Use
step_normalize() to center and scale all the non-categorical predictor variables. (Do this BEFORE creating dummy variables. When I tried to do it after, I ran into an error - I’m still investigating why.)
- Create dummy variables for all factors/categorical predictor variables (make sure you have
-all_outcomes() in this part!!).
- Use the
prep() and juice() functions to apply the steps to the training data just to check that everything went as planned.
hotels_recipe <- recipe(is_canceled ~ ., data = hotels_mod) %>%
step_mutate_at(children, babies, previous_cancellations, fn = function(x) as.numeric(x > 0)) %>%
step_mutate_at(agent, company, fn = function(x) as.numeric(x == "NULL")) %>%
step_mutate(country = fct_lump_n(country, 5)) %>%
step_normalize(all_numeric()) %>%
step_dummy(all_nominal(), -all_outcomes())
hotels_recipe %>%
prep(hotels_training) %>%
juice()
- In this step we will set up a LASSO model and workflow.
- In general, why would we want to use LASSO instead of regular logistic regression? (HINT: think about what happens to the coefficients).
We would choose LASSO over regular logistic regression because we will be doing model selection in the process. LASSO is a shrinkage method that sets the coefficient of insignificant variables to 0, thereby eliminating those variables from the set of predictors. With regular logistic regression, we would have to manually test each predictor or take a stepwise approach.
- Define the model type, set the engine, set the
penalty argument to tune() as a placeholder, and set the mode.
hotels_lasso_mod <-
logistic_reg(mixture = 1) %>%
set_engine("glmnet") %>%
set_args(penalty = tune()) %>%
set_mode("classification")
- Create a workflow with the recipe and model.
(hotels_lasso_wf <-
workflow() %>%
add_recipe(hotels_recipe) %>%
add_model(hotels_lasso_mod))
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 5 Recipe Steps
##
## ● step_mutate_at()
## ● step_mutate_at()
## ● step_mutate()
## ● step_normalize()
## ● step_dummy()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
##
## Main Arguments:
## penalty = tune()
## mixture = 1
##
## Computational engine: glmnet
- In this step, we’ll tune the model and fit the model using the best tuning parameter to the entire training dataset.
- Create a 5-fold cross-validation sample. We’ll use this later. I have set the seed for you.
set.seed(494) # for reproducibility
hotels_cv <- vfold_cv(hotels_training, v = 5)
- Use the
grid_regular() function to create a grid of 10 potential penalty parameters (we’re keeping this sort of small because the dataset is pretty large). Use that with the 5-fold cv data to tune the model.
penalty_grid <- grid_regular(penalty(), levels = 10)
- Use the
tune_grid() function to fit the models with different tuning parameters to the different cross-validation sets.
hotels_lasso_tune <-
hotels_lasso_wf %>%
tune_grid(resamples = hotels_cv, grid = penalty_grid)
hotels_lasso_tune
- Use the
collect_metrics() function to collect all the metrics from the previous step and create a plot with the accuracy on the y-axis and the penalty term on the x-axis. Put the x-axis on the log scale.
hotels_lasso_tune %>%
collect_metrics() %>%
filter(.metric == "accuracy") %>%
ggplot(aes(x = penalty, y = mean)) +
geom_point() +
geom_line() +
scale_x_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10",scales::math_format(10^.x))) +
labs(x = "penalty", y = "accuracy")

- Use the
select_best() function to find the best tuning parameter, fit the model using that tuning parameter to the entire training set (HINT: finalize_workflow() and fit()), and display the model results using pull_workflow_fit() and tidy(). Are there some variables with coefficients of 0?
(best_param <- hotels_lasso_tune %>%
select_best(metric = "accuracy"))
(hotels_lasso_final_wf <- hotels_lasso_wf %>%
finalize_workflow(best_param))
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 5 Recipe Steps
##
## ● step_mutate_at()
## ● step_mutate_at()
## ● step_mutate()
## ● step_normalize()
## ● step_dummy()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
##
## Main Arguments:
## penalty = 1e-10
## mixture = 1
##
## Computational engine: glmnet
hotels_lasso_final_mod <- hotels_lasso_final_wf %>%
fit(data = hotels_training)
hotels_lasso_final_mod %>%
pull_workflow_fit() %>%
tidy()
hotels_lasso_final_mod %>%
pull_workflow_fit() %>%
tidy() %>%
filter(estimate == 0)
Yes, four variables had a coefficient set to 0.
- Now that we have a model, let’s evaluate it a bit more. All we have looked at so far is the cross-validated accuracy from the previous step.
- Create a variable importance graph. Which variables show up as the most important? Are you surprised?
hotels_lasso_final_mod %>%
pull_workflow_fit() %>%
vip()

It is interesting to see what non-refundable bookings were most likely to get canceled, when compared to those that were refundable and didn’t require a deposit. I would’ve thought that people were more likely to keep their bookings if canceling meant they would lose their deposits. Definitely surprised by how the more important variables are categories of the assigned and reserved room types, considering these had so many categories each and I initially thought they were quite arbitrary. It does make sense that people might cancel because they were unsatisfied with the room they were assigned with. For future work, it might be worthwhile to create an indicator variable for whether the assigned room type matched the reserved. Here, I quickly investigated how many bookings did and did not receive the room type they reserved:
hotels %>%
mutate(received_reserved_room_type = reserved_room_type == assigned_room_type) %>%
count(received_reserved_room_type, name = "n") %>%
mutate(p = n/nrow(hotels))
- Use the
last_fit() function to fit the final model and then apply it to the testing data. Report the metrics from the testing data using the collet_metrics() function. How do they compare to the cross-validated metrics?
hotels_lasso_test <- hotels_lasso_final_wf %>%
last_fit(hotels_split)
hotels_lasso_test %>%
collect_metrics()
hotels_lasso_tune %>%
collect_metrics() %>%
filter(penalty == best_param$penalty)
The metrics from the testing data are slightly higher than the cross-validated metrics corresponding to the best penalty value.
- Use the
collect_predictions() function to find the predicted probabilities and classes for the test data. Save this to a new dataset called preds. Then, use the conf_mat() function from dials (part of tidymodels) to create a confusion matrix showing the predicted classes vs. the true classes. What is the true positive rate (sensitivity)? What is the true negative rate (specificity)? See this Wikipedia reference if you (like me) tend to forget these definitions.
preds <- collect_predictions(hotels_lasso_test)
conf_mat(preds, truth = is_canceled, estimate = .pred_class)
## Truth
## Prediction 0 1
## 0 34225 7782
## 1 3340 14346
Sensitivity = 14346/(14346 + 7782) = 0.6483189
Specificity = 34225/(34225 + 3340) = 0.9110874
Use the preds dataset you just created to create a density plot of the predicted probabilities of canceling (the variable is called .pred_1), filling by is_canceled. Use an alpha = .5 and color = NA in the geom_density(). Answer these questions: a. What would this graph look like for a model with an accuracy that was close to 1? b. Our predictions are classified as canceled if their predicted probability of canceling is greater than .5. If we wanted to have a high true positive rate, should we make the cutoff for predicted as canceled higher or lower than .5? c. What happens to the true negative rate if we try to get a higher true positive rate?
ggplot(preds, aes(x = .pred_1, fill = is_canceled)) +
geom_density(alpha = .5, color = NA) +
labs(x = "Predicted probability", y = "Density", fill = "Is canceled")

- Predictions from a model with an accuracy that was close to 1 would be more accurate. For bookings that were actually canceled, the predictions would be closer to 1 and there would be more density concentrated in the rightmost part of the graph. Likewise, for bookings that weren’t canceled, their predictions would be closer to 0 and most of the density would be in the leftmost part.
- If we wanted to increase sensitivity, we should make the cutoff less than .5.
- The true negative rate would decrease if the true positive rate increases.
- Let’s say that this model is going to be applied to bookings 14 days in advance of their arrival at each hotel, and someone who works for the hotel will make a phone call to the person who made the booking. During this phone call, they will try to assure that the person will be keeping their reservation or that they will be canceling in which case they can do that now and still have time to fill the room. How should the hotel go about deciding who to call? How could they measure whether it was worth the effort to do the calling? Can you think of another way they might use the model?
To save time, the hotel can run the bookings through the model to find their probability of being canceled and prioritize calling people who made the bookings with the highest probabilities. They can also find bookings with attributes considered important by the model (e.g. reserving room type P, being non-refundable, etc.) and verify those first. If those bookings are confirmed to be canceled then it was worth the effort since the hotel can then immediately open up the room for reservation. In general, they can compare the final status of the bookings (canceled vs. not canceled) to the outcome predicted by the model to evaluate.
- How might you go about questioning and evaluating the model in terms of fairness? Are there any questions you would like to ask of the people who collected the data?
I think the first step is to examine the data and see whether it is representative of most bookings if we are to use the model on bookings made by other hotels not in the dataset. We can test the model on new data (if available) and re-evaluate its metrics to ensure that it is still producing accurate results. Since the data came from two different hotels, we might want to ask about the demographics that most commonly stay at each place. For example, we have a hotel that is located in the city and one that’s a beach resort, so the former might be more popular among people on business trips while the latter among those on vacation. Furthermore, one might be more popular with international travelers while the other with domestic. One group might be more or less likely to cancel so it might be worthwhile to check before we generalize the model to a heterogeneous population.
LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMScKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAjIGZvciBncmFwaGluZyBhbmQgZGF0YSBjbGVhbmluZwpsaWJyYXJ5KHRpZHltb2RlbHMpICAgICAgICAjIGZvciBtb2RlbGluZwpsaWJyYXJ5KG5hbmlhcikgICAgICAgICAgICAjIGZvciBhbmFseXppbmcgbWlzc2luZyB2YWx1ZXMKbGlicmFyeSh2aXApICAgICAgICAgICAgICAgIyBmb3IgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90cwp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjIExpc2EncyBmYXZvcml0ZSB0aGVtZQpgYGAKCmBgYHtyIGRhdGF9CmhvdGVscyA8LSByZWFkcjo6cmVhZF9jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvbWFzdGVyL2RhdGEvMjAyMC8yMDIwLTAyLTExL2hvdGVscy5jc3YnKQpgYGAKCgpXaGVuIHlvdSBmaW5pc2ggdGhlIGFzc2lnbm1lbnQsIHJlbW92ZSB0aGUgYCNgIGZyb20gdGhlIG9wdGlvbnMgY2h1bmsgYXQgdGhlIHRvcCwgc28gdGhhdCBtZXNzYWdlcyBhbmQgd2FybmluZ3MgYXJlbid0IHByaW50ZWQuIElmIHlvdSBhcmUgZ2V0dGluZyBlcnJvcnMgaW4geW91ciBjb2RlLCBhZGQgYGVycm9yID0gVFJVRWAgc28gdGhhdCB0aGUgZmlsZSBrbml0cy4gSSB3b3VsZCByZWNvbW1lbmQgbm90IHJlbW92aW5nIHRoZSBgI2AgdW50aWwgeW91IGFyZSBjb21wbGV0ZWx5IGZpbmlzaGVkLgoKIyMgU2V0dGluZyB1cCBHaXQgYW5kIEdpdEh1YiBpbiBSU3R1ZGlvCgpSZWFkIHRoZSBbUXVpY2sgSW50cm9dKGh0dHBzOi8vYWR2YW5jZWQtZHMtaW4tci5uZXRsaWZ5LmFwcC9wb3N0cy8yMDIxLTAxLTI4LWdpdGdpdGh1Yi8jcXVpY2staW50cm8pIHNlY3Rpb24gb2YgdGhlIFVzaW5nIGdpdCBhbmQgR2l0SHViIGluIFIgU3R1ZGlvIHNldCBvZiBDb3Vyc2UgTWF0ZXJpYWxzLiBTZXQgdXAgR2l0IGFuZCBHaXRIdWIgYW5kIGNyZWF0ZSBhIEdpdEh1YiByZXBvIGFuZCBhc3NvY2lhdGVkIFIgUHJvamVjdCAoZG9uZSBmb3IgeW91IHdoZW4geW91IGNsb25lIHRoZSByZXBvKSBmb3IgdGhpcyBob21ld29yayBhc3NpZ25tZW50LiBQdXQgdGhpcyBmaWxlIGludG8gdGhlIHByb2plY3QuIFlvdSBzaG91bGQgYWx3YXlzIG9wZW4gdGhlIFIgUHJvamVjdCAoLlJwcm9qKSBmaWxlIHdoZW4geW91IHdvcmsgd2l0aCBhbnkgb2YgdGhlIGZpbGVzIGluIHRoZSBwcm9qZWN0LiAKCioqVGFzayoqOiBCZWxvdywgcG9zdCBhIGxpbmsgdG8geW91ciBHaXRIdWIgcmVwb3NpdG9yeS4KCmh0dHBzOi8vZ2l0aHViLmNvbS90aHl0bmcvc3RhdDQ5NC1hc3NpZ25tZW50MQoKIyMgQ3JlYXRpbmcgYSB3ZWJzaXRlCgpZb3UnbGwgYmUgdXNpbmcgUlN0dWRpbyB0byBjcmVhdGUgYSBwZXJzb25hbCB3ZWJzaXRlIHRvIHNob3djYXNlIHlvdXIgd29yayBmcm9tIHRoaXMgY2xhc3MhIFN0YXJ0IGJ5IHdhdGNoaW5nIHRoZSBbU2hhcmluZyBvbiBTaG9ydCBOb3RpY2VdKGh0dHBzOi8vcnN0dWRpby5jb20vcmVzb3VyY2VzL3dlYmluYXJzL3NoYXJpbmctb24tc2hvcnQtbm90aWNlLWhvdy10by1nZXQteW91ci1tYXRlcmlhbHMtb25saW5lLXdpdGgtci1tYXJrZG93bi8pIHdlYmluYXIgYnkgQWxpc29uIEhpbGwgYW5kIERlc2lyw6llIERlIExlb24gb2YgUlN0dWRpby4gVGhpcyBzaG91bGQgaGVscCB5b3UgY2hvb3NlIHRoZSB0eXBlIG9mIHdlYnNpdGUgeW91J2QgbGlrZSB0byBjcmVhdGUuIAoKT25jZSB5b3UndmUgY2hvc2VuIHRoYXQsIHlvdSBtaWdodCB3YW50IHRvIGxvb2sgdGhyb3VnaCBzb21lIG9mIHRoZSBvdGhlciAqQnVpbGRpbmcgYSB3ZWJzaXRlKiByZXNvdXJjZXMgSSBwb3N0ZWQgb24gdGhlIFtyZXNvdXJjZXMgcGFnZV0oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Jlc291cmNlcy5odG1sKSBvZiBvdXIgY291cnNlIHdlYnNpdGUuIEkgaGlnaGx5IHJlY29tbWVuZCBtYWtpbmcgYSBuaWNlIGxhbmRpbmcgcGFnZSB3aGVyZSB5b3UgZ2l2ZSBhIGJyaWVmIGludHJvZHVjdGlvbiBvZiB5b3Vyc2VsZi4gCgoKKipUYXNrcyoqOgoKKiBJbmNsdWRlIGEgbGluayB0byB5b3VyIHdlYnNpdGUgYmVsb3cuIChJZiBhbnlvbmUgZG9lcyBub3Qgd2FudCB0byBwb3N0IGEgd2Vic2l0ZSBwdWJsaWNseSwgcGxlYXNlIHRhbGsgdG8gbWUgYW5kIHdlIHdpbGwgZmluZCBhIGRpZmZlcmVudCBzb2x1dGlvbikuICAKaHR0cHM6Ly90aHktbmd1eWVuLm5ldGxpZnkuYXBwCgoqIExpc3RlbiB0byBhdCBsZWFzdCB0aGUgZmlyc3QgMjAgbWludXRlcyBvZiAiQnVpbGRpbmcgYSBDYXJlZXIgaW4gRGF0YSBTY2llbmNlLCBDaGFwdGVyIDQ6IEJ1aWxkaW5nIGEgUG9ydGZvbGlvIi4gR28gdG8gdGhlIG1haW4gW3BvZGNhc3Qgd2Vic2l0ZV0oaHR0cHM6Ly9wb2RjYXN0LmJlc3Rib29rLmNvb2wvKSBhbmQgbmF2aWdhdGUgdG8gYSBwb2RjYXN0IHByb3ZpZGVyIHRoYXQgd29ya3MgZm9yIHlvdSB0byBmaW5kIHRoYXQgc3BlY2lmaWMgZXBpc29kZS4gV3JpdGUgMi0zIHNlbnRlbmNlcyByZWZsZWN0aW5nIG9uIHdoYXQgdGhleSBkaXNjdXNzZWQgYW5kIHdoeSBjcmVhdGluZyBhIHdlYnNpdGUgbWlnaHQgYmUgaGVscGZ1bCBmb3IgeW91LiAgCioqSSBsaXN0ZW5lZCB0byB0aGUgZmlyc3QgMjAgbWludXRlcyBvZiB0aGUgZXBpc29kZS4gVGhlIGhvc3RzIGRpc2N1c3NlZCB3aHkgcGVvcGxlLCBlc3BlY2lhbGx5IHRob3NlIGxvb2tpbmcgZm9yIHRoZWlyIGZpcnN0IGpvYiwgc2hvdWxkIGNyZWF0ZSB0aGVpciBvd24gZGF0YSBzY2llbmNlIHBvcnRmb2xpby4gV2hlbiBvbmUgb2YgdGhlbSB0YWxrZWQgYWJvdXQgaG93IHlvdXIgcmVzdW1lIGFsb25lIG1pZ2h0IG5vdCBwcmVzZW50IHlvdSBhcyBhIGRhdGEgc2NpZW50aXN0LCBJIGRlZmluaXRlbHkgZmVsdCBsaWtlIHRoaXMgd2FzIHRydWUgZm9yIG1lIGJlY2F1c2UgYWxsIG9mIG15IHdvcmsgZXhwZXJpZW5jZSBkbyBub3Qgc2NyZWFtIGRhdGEgc2NpZW5jZSBldmVuIHRob3VnaCBJIGRpZCBkbyBkYXRhIHdyYW5nbGluZywgdmlzdWFsaXphdGlvbiwgYW5kIG90aGVyIHRoaW5ncyB0aGF0IHlvdSdkIGV4cGVjdCB0byBzZWUgaW4gYSBEUyBwcm9qZWN0LiBBbHRob3VnaCBJIGNhbid0IHNoYXJlIHRoZSBjb2RlIGFuZCByZXN1bHRzIG9mIHdoYXQgSSB3b3JrZWQgb24gYmVjYXVzZSBvZiBwcml2YWN5IGlzc3VlcywgSSB0aGluayBiZWluZyBhYmxlIHRvIGVsYWJvcmF0ZSBvbiB3aGF0IEkgZGlkLCB0aGUgc29mdHdhcmUgSSB1c2VkLCBteSB0YWtlYXdheXMsIGV0Yy4gaW4gYSBibG9nIHBvc3Qgd291bGQgYmV0dGVyIGRlbW9uc3RyYXRlIG15IGFiaWxpdGllcyB0aGFuIGEgY291cGxlIG9mIGJ1bGxldCBwb2ludHMgaW4gbXkgcmVzdW1lLiBBbHNvLCBiZWNhdXNlIEkgY2FuJ3Qgc2hvdyBteSB3b3JrIGZvciByZXNlYXJjaCBwcm9qZWN0cywgSSB3YW50IHRvIGRpZyB1cCBvbGQgd29yayBmb3IgY2xhc3NlcyBhbmQgc2VlIHdoZXRoZXIgSSBjYW4gYWRkIHRvIHRoZW0gb3Igc2ltcGx5IG1ha2UgdGhlbSBtb3JlIHByZXNlbnRhYmxlIHRvIHBvc3Qgb24gbXkgc2l0ZSwgYW5kIHRoaXMgaXMgYSBncmVhdCBjaGFuY2UgZm9yIG1lIHRvIHJldmlldyBhbmQgcmVmbGVjdCBvbiB0aGUgdGhpbmdzIEkndmUgbGVhcm5lZCBhbmQgZG9uZS4gSSB3YXMgdmVyeSBzdXJwcmlzZWQgd2hlbiB0aGV5IHRhbGtlZCBhYm91dCBob3cgZGVtb25zdHJhdGluZyB5b3VyIHNraWxscyBjb3VsZCBiZSBzaW1wbGUgYXMgc2hvd2luZyBiYXNpYyBkYXRhIGNsZWFuaW5nLCB3cmFuZ2xpbmcsIHZpeiwgZXRjLiBTaW5jZSBJIGRvIGZpbmQgbXlzZWxmIHF1ZXN0aW9uaW5nIHdoZXRoZXIgdGhpbmdzIEkgZGlkIGFyZSB3b3J0aCBzaG93aW5nIG9mZiB0byB0aGUgcHVibGljLCBoZWFyaW5nIHRoYXQgc29tZXRpbWVzIGVtcGxveWVycyBqdXN0IHdhbnQgdG8ga25vdyB3aGV0aGVyIEkgY2FuIGNvZGUgd2FzIHF1aXRlIHJlYXNzdXJpbmcuKioKCiogKE9wdGlvbmFsKSBDcmVhdGUgYW4gUiBwYWNrYWdlIHdpdGggeW91ciBvd24gY3VzdG9taXplZCBgZ3BwbG90MmAgdGhlbWUhIFdyaXRlIGEgcG9zdCBvbiB5b3VyIHdlYnNpdGUgYWJvdXQgd2h5IHlvdSBtYWRlIHRoZSBjaG9pY2VzIHlvdSBkaWQgZm9yIHRoZSB0aGVtZS4gU2VlIHRoZSAqQnVpbGRpbmcgYW4gUiBwYWNrYWdlKiBhbmQgKkN1c3RvbSBgZ2dwbG90MmAgdGhlbWVzKiBbcmVzb3VyY2VzXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcmVzb3VyY2VzLmh0bWwpLiAKCiMjIE1hY2hpbmUgTGVhcm5pbmcgcmV2aWV3IGFuZCBpbnRybyB0byBgdGlkeW1vZGVsc2AKClJlYWQgdGhyb3VnaCBhbmQgZm9sbG93IGFsb25nIHdpdGggdGhlIFtNYWNoaW5lIExlYXJuaW5nIHJldmlldyB3aXRoIGFuIGludHJvIHRvIHRoZSBgdGlkeW1vZGVsc2AgcGFja2FnZV0oaHR0cHM6Ly9hZHZhbmNlZC1kcy1pbi1yLm5ldGxpZnkuYXBwL3Bvc3RzLzIwMjEtMDMtMTYtbWwtcmV2aWV3LykgcG9zdGVkIG9uIHRoZSBDb3Vyc2UgTWF0ZXJpYWxzIHBhZ2UuIAoKKipUYXNrcyoqOgoKMS4gUmVhZCBhYm91dCB0aGUgaG90ZWwgYm9va2luZyBkYXRhLCBgaG90ZWxzYCwgb24gdGhlIFtUaWR5IFR1ZXNkYXkgcGFnZV0oaHR0cHM6Ly9naXRodWIuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9ibG9iL21hc3Rlci9kYXRhLzIwMjAvMjAyMC0wMi0xMS9yZWFkbWUubWQpIGl0IGNhbWUgZnJvbS4gVGhlcmUgaXMgYWxzbyBhIGxpbmsgdG8gYW4gYXJ0aWNsZSBmcm9tIHRoZSBvcmlnaW5hbCBhdXRob3JzLiBUaGUgb3V0Y29tZSB3ZSB3aWxsIGJlIHByZWRpY3RpbmcgaXMgY2FsbGVkIGBpc19jYW5jZWxlZGAuIAoKICAtIFdpdGhvdXQgZG9pbmcgYW55IGFuYWx5c2lzLCB3aGF0IGFyZSBzb21lIHZhcmlhYmxlcyB5b3UgdGhpbmsgbWlnaHQgYmUgcHJlZGljdGl2ZSBhbmQgd2h5PyAqKlRoZSB2YXJpYWJsZXMgdGhhdCBzdG9vZCBvdXQgdG8gbWUgdGhlIG1vc3Qgd2VyZSBgcHJldmlvdXNfY2FuY2VsbGF0aW9uc2AgYW5kIGBwcmV2aW91c19ib29raW5nc19ub3RfY2FuY2VsZWRgLiBUaGVzZSBnaXZlIHVzIGEgZ2xpbXBzZSBpbnRvIHRoZWlyIGhpc3RvcnkgYW5kIGlmIHRoZXkgaGF2ZSBjYW5jZWxlZCBtdWx0aXBsZSB0aW1lcyBpbiB0aGUgcGFzdCB0aGVuIHRoZXkgbWlnaHQgZG8gaXQgYWdhaW4uIGBpc19yZXBlYXRlZF9ndWVzdGAgaXMgYWxzbyBhIHBvc3NpYmxlIGluZGljYXRvci4gR3Vlc3RzIHdobyBoYXZlIHN0YXllZCBpbiB0aGUgaG90ZWwgbWlnaHQgYmUgbW9yZSByZWxpYWJsZSB0byB1cGhvbGQgdGhlaXIgcmVzZXJ2YXRpb24uKioKCiAgLSBXaGF0IGFyZSBzb21lIHByb2JsZW1zIHRoYXQgbWlnaHQgZXhpc3Qgd2l0aCB0aGUgZGF0YT8gWW91IG1pZ2h0IHRoaW5rIGFib3V0IGhvdyBpdCB3YXMgY29sbGVjdGVkIGFuZCB3aG8gZGlkIHRoZSBjb2xsZWN0aW5nLiAqKlRoZSBkYXRhIGNhbWUgZnJvbSB0d28gaG90ZWxzLCB3aGljaCBtaWdodCBub3QgYmUgcmVwcmVzZW50YXRpdmUgb2YgYm9va2luZ3MgbWFkZSBieSBvdGhlciBwbGFjZXMgb2ZmZXJpbmcgYWNjb21tb2RhdGlvbi4qKgogIAogIC0gSWYgd2UgY29uc3RydWN0IGEgbW9kZWwsIHdoYXQgdHlwZSBvZiBjb25jbHVzaW9ucyB3aWxsIGJlIGFibGUgdG8gZHJhdyBmcm9tIGl0PyAKKipXZSBtaWdodCBiZSBhYmxlIHRvIGRldGVybWluZSBwcmVkaWN0b3IocykgZm9yIG91ciByZXNwb25zZSB2YXJpYWJsZSwgYGlzX2NhbmNlbGVkYCwgYW5kIGFzc2VzcyB0aGUgc2lnbmlmaWNhbmNlLioqCiAgCjIuIENyZWF0ZSBzb21lIGV4cGxvcmF0b3J5IHBsb3RzIG9yIHRhYmxlIHN1bW1hcmllcyBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0LiBCZSBzdXJlIHRvIGFsc28gZXhhbWluZSBtaXNzaW5nIHZhbHVlcyBvciBvdGhlciBpbnRlcmVzdGluZyB2YWx1ZXMuCgoqKkxvb2tpbmcgYXQgbnVtZXJpY2FsIHZhcmlhYmxlczoqKgoKYGBge3IgZXhwbF9xdWFudH0KaG90ZWxzICU+JSAKICBzZWxlY3QoLWlzX2NhbmNlbGVkLCAtaXNfcmVwZWF0ZWRfZ3Vlc3QpICU+JQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsIAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwKSArCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwgCiAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKKiphbmQgY2F0ZWdvcmljYWw6KioKCmBgYHtyIGV4cGxfY2F0LCBmaWcud2lkdGggPSAxMH0KaG90ZWxzICU+JSAKICAjIHRoZXNlIHZhcmlhYmxlcyBoYXZlIG1hbnkgY2F0ZWdvcmllcyBhbmQgc2hvd2luZyB0aGVtIGRvZXNuJ3QgY29udmV5IGFueSBpbmZvcm1hdGlvbiAKICBzZWxlY3QoLWFnZW50LCAtY291bnRyeSwgLWNvbXBhbnkpICU+JSAKICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBhcy5mYWN0b3IpICU+JQogICMgZG9pbmcgdGhpcyBzbyB0aGF0IHRoZSBwbG90IGRpc3BsYXlzIHRoZSBtb250aHMgaW4gY2hyb25vbG9naWNhbCBvcmRlcgogIG11dGF0ZShhcnJpdmFsX2RhdGVfbW9udGggPSBmYWN0b3IoYXJyaXZhbF9kYXRlX21vbnRoLCBsZXZlbHMgPSBtb250aC5uYW1lKSwKICAgICAgICAgaXNfcmVwZWF0ZWRfZ3Vlc3QgPSBmYWN0b3IoaXNfcmVwZWF0ZWRfZ3Vlc3QpKSAlPiUKICBzZWxlY3Qod2hlcmUoaXMuZmFjdG9yKSkgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsIAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gdmFsdWUpKSArCiAgZ2VvbV9iYXIoKSArCiAgZmFjZXRfd3JhcCh2YXJzKHZhcmlhYmxlKSwgCiAgICAgICAgICAgICBzY2FsZXMgPSAiZnJlZSIsIAogICAgICAgICAgICAgbnJvdyA9IDIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQwLCBoanVzdCA9IDEsIHNpemUgPSA4LjUpKQpgYGAKCioqVGhlIHBsb3RzIGZvciBgYWdlbnRgLCBgY29tcGFueWAsIGFuZCBgY291bnRyeWAgbG9va2VkIHZlcnkgbWVzc3kgYmVjYXVzZSBlYWNoIGhhcyBzbyBtYW55IGNhdGVnb3JpZXMuIEluc3RlYWQgb2YgcGxvdHRpbmcgd2UgY2FuIGxvb2sgYXQgdGhlaXIgdGFibGUgc3VtbWFyaWVzOioqCgpgYGB7cn0KaG90ZWxzICU+JQogIGNvdW50KGFnZW50KSAlPiUKICBhcnJhbmdlKGRlc2MobikpICU+JQogIGhlYWQoNSkKaG90ZWxzICU+JQogIGNvdW50KGNvbXBhbnkpICU+JQogIGFycmFuZ2UoZGVzYyhuKSkgJT4lCiAgaGVhZCg1KQpob3RlbHMgJT4lCiAgY291bnQoY291bnRyeSkgJT4lCiAgYXJyYW5nZShkZXNjKG4pKSAlPiUKICBoZWFkKDUpCmBgYAoKKipDaGVja2luZyBmb3IgbWlzc2luZyBkYXRhOioqCgpgYGB7cn0KaG90ZWxzICU+JSAKICBhZGRfbl9taXNzKCkgJT4lIAogIGNvdW50KG5fbWlzc19hbGwpCmBgYAoKKipTb21lIG9ic2VydmF0aW9ucyBJIGhhdmUgYXJlOioqCgoqICoqVGhlcmUgYXJlIGxvdHMgb2YgMHMgaW4gbWFueSBudW1lcmljYWwgdmFyaWFibGVzIHN1Y2ggYXMgYGFkdWx0c2AsIGBiYWJpZXNgLCBgYm9va2luZ19jaGFuZ2VzYCwgZXRjLioqCiogKipBdmVyYWdlIGRhaWx5IHJhdGUgYGFkcmAgc2VlbXMgdG8gYmUgdmVyeSBsZWZ0LXNrZXdlZC4qKgoqICoqTWFueSBib29raW5ncyBoYXZlIE5VTEwgZm9yIGBhZ2VudGAgYW5kIGBjb21wYW55YC4qKgoqICoqTW9zdCBvYnNlcnZhdGlvbnMgaW4gdGhlIGRhdGFzZXQgYXJlIGNvbXBsZXRlIGFuZCB0aGUgZm91ciBpbmNvbXBsZXRlIG9uZXMgYXJlIG9ubHkgbWlzc2luZyBvbmUgdmFsdWUgZWFjaC4qKgoKMy4gRmlyc3QsIHdlIHdpbGwgZG8gYSBjb3VwbGUgdGhpbmdzIHRvIGdldCB0aGUgZGF0YSByZWFkeSwgaW5jbHVkaW5nIG1ha2luZyB0aGUgb3V0Y29tZSBhIGZhY3RvciAobmVlZHMgdG8gYmUgdGhhdCB3YXkgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24pLCByZW1vdmluZyB0aGUgeWVhciB2YXJpYWJsZSBhbmQgc29tZSByZXNlcnZhdGlvbiBzdGF0dXMgdmFyaWFibGVzLCBhbmQgcmVtb3ZpbmcgbWlzc2luZyB2YWx1ZXMgKG5vdCBOVUxMcyBidXQgdHJ1ZSBtaXNzaW5nIHZhbHVlcykuIFNwbGl0IHRoZSBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQsIHN0cmF0aWZ5aW5nIG9uIHRoZSBvdXRjb21lIHZhcmlhYmxlLCBgaXNfY2FuY2VsZWRgLiBTaW5jZSB3ZSBoYXZlIGEgbG90IG9mIGRhdGEsIHdlJ3JlIGdvaW5nIHRvIHNwbGl0IHRoZSBkYXRhIDUwLzUwIGJldHdlZW4gdHJhaW5pbmcgYW5kIHRlc3QuIEkgaGF2ZSBhbHJlYWR5IGBzZXQuc2VlZCgpYCBmb3IgeW91LiBCZSBzdXJlIHRvIHVzZSBgaG90ZWxzX21vZGAgaW4gdGhlIHNwbGl0dGluZy4KCmBgYHtyfQpob3RlbHNfbW9kIDwtIGhvdGVscyAlPiUgCiAgbXV0YXRlKGlzX2NhbmNlbGVkID0gYXMuZmFjdG9yKGlzX2NhbmNlbGVkKSkgJT4lIAogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgYXMuZmFjdG9yKSkgJT4lIAogIHNlbGVjdCgtYXJyaXZhbF9kYXRlX3llYXIsCiAgICAgICAgIC1yZXNlcnZhdGlvbl9zdGF0dXMsCiAgICAgICAgIC1yZXNlcnZhdGlvbl9zdGF0dXNfZGF0ZSkgJT4lIAogIGFkZF9uX21pc3MoKSAlPiUgCiAgZmlsdGVyKG5fbWlzc19hbGwgPT0gMCkgJT4lIAogIHNlbGVjdCgtbl9taXNzX2FsbCkKCnNldC5zZWVkKDQ5NCkKYGBgCgpgYGB7cn0KaG90ZWxzX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoaG90ZWxzX21vZCwgcHJvcCA9IC41KQpob3RlbHNfdHJhaW5pbmcgPC0gdHJhaW5pbmcoaG90ZWxzX3NwbGl0KQpob3RlbHNfdGVzdGluZyA8LSB0ZXN0aW5nKGhvdGVsc19zcGxpdCkKYGBgCgo0LiBJbiB0aGlzIG5leHQgc3RlcCwgd2UgYXJlIGdvaW5nIHRvIGRvIHRoZSBwcmUtcHJvY2Vzc2luZy4gVXN1YWxseSwgSSB3b24ndCB0ZWxsIHlvdSBleGFjdGx5IHdoYXQgdG8gZG8gaGVyZSwgYnV0IGZvciB5b3VyIGZpcnN0IGV4ZXJjaXNlLCBJJ2xsIHRlbGwgeW91IHRoZSBzdGVwcy4gCgoqIFNldCB1cCB0aGUgcmVjaXBlIHdpdGggYGlzX2NhbmNlbGVkYCBhcyB0aGUgb3V0Y29tZSBhbmQgYWxsIG90aGVyIHZhcmlhYmxlcyBhcyBwcmVkaWN0b3JzIChISU5UOiBgfi5gKS4gIAoqIFVzZSBhIGBzdGVwX1hYWCgpYCBmdW5jdGlvbiBvciBmdW5jdGlvbnMgKEkgdGhpbmsgdGhlcmUgYXJlIG90aGVyIHdheXMgdG8gZG8gdGhpcywgYnV0IEkgZm91bmQgYHN0ZXBfbXV0YXRlX2F0KClgIGVhc2llc3QpIHRvIGNyZWF0ZSBzb21lIGluZGljYXRvciB2YXJpYWJsZXMgZm9yIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOiBgY2hpbGRyZW5gLCBgYmFiaWVzYCwgYW5kIGBwcmV2aW91c19jYW5jZWxsYXRpb25zYC4gU28sIHRoZSBuZXcgdmFyaWFibGUgc2hvdWxkIGJlIGEgMSBpZiB0aGUgb3JpZ2luYWwgaXMgbW9yZSB0aGFuIDAgYW5kIDAgb3RoZXJ3aXNlLiBNYWtlIHN1cmUgeW91IGRvIHRoaXMgaW4gYSB3YXkgdGhhdCBhY2NvdW50cyBmb3IgdmFsdWVzIHRoYXQgbWF5IGJlIGxhcmdlciB0aGFuIGFueSB3ZSBzZWUgaW4gdGhlIGRhdGFzZXQuICAKKiBGb3IgdGhlIGBhZ2VudGAgYW5kIGBjb21wYW55YCB2YXJpYWJsZXMsIG1ha2UgbmV3IGluZGljYXRvciB2YXJpYWJsZXMgdGhhdCBhcmUgMSBpZiB0aGV5IGhhdmUgYSB2YWx1ZSBvZiBgTlVMTGAgYW5kIDAgb3RoZXJ3aXNlLiAKKiBVc2UgYGZjdF9sdW1wX24oKWAgdG8gbHVtcCB0b2dldGhlciBjb3VudHJpZXMgdGhhdCBhcmVuJ3QgaW4gdGhlIHRvcCA1IG1vc3Qgb2NjdXJyaW5nLiAKKiBJZiB5b3UgdXNlZCBuZXcgbmFtZXMgZm9yIHNvbWUgb2YgdGhlIG5ldyB2YXJpYWJsZXMgeW91IGNyZWF0ZWQsIHRoZW4gcmVtb3ZlIGFueSB2YXJpYWJsZXMgdGhhdCBhcmUgbm8gbG9uZ2VyIG5lZWRlZC4gCiogVXNlIGBzdGVwX25vcm1hbGl6ZSgpYCB0byBjZW50ZXIgYW5kIHNjYWxlIGFsbCB0aGUgbm9uLWNhdGVnb3JpY2FsIHByZWRpY3RvciB2YXJpYWJsZXMuIChEbyB0aGlzIEJFRk9SRSBjcmVhdGluZyBkdW1teSB2YXJpYWJsZXMuIFdoZW4gSSB0cmllZCB0byBkbyBpdCBhZnRlciwgSSByYW4gaW50byBhbiBlcnJvciAtIEknbSBzdGlsbCBpbnZlc3RpZ2F0aW5nIHdoeS4pCiogQ3JlYXRlIGR1bW15IHZhcmlhYmxlcyBmb3IgYWxsIGZhY3RvcnMvY2F0ZWdvcmljYWwgcHJlZGljdG9yIHZhcmlhYmxlcyAobWFrZSBzdXJlIHlvdSBoYXZlIGAtYWxsX291dGNvbWVzKClgIGluIHRoaXMgcGFydCEhKS4gIAoqIFVzZSB0aGUgYHByZXAoKWAgYW5kIGBqdWljZSgpYCBmdW5jdGlvbnMgdG8gYXBwbHkgdGhlIHN0ZXBzIHRvIHRoZSB0cmFpbmluZyBkYXRhIGp1c3QgdG8gY2hlY2sgdGhhdCBldmVyeXRoaW5nIHdlbnQgYXMgcGxhbm5lZC4KCmBgYHtyfQpob3RlbHNfcmVjaXBlIDwtIHJlY2lwZShpc19jYW5jZWxlZCB+IC4sIGRhdGEgPSBob3RlbHNfbW9kKSAlPiUKICBzdGVwX211dGF0ZV9hdChjaGlsZHJlbiwgYmFiaWVzLCBwcmV2aW91c19jYW5jZWxsYXRpb25zLCBmbiA9IGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoeCA+IDApKSAlPiUKICBzdGVwX211dGF0ZV9hdChhZ2VudCwgY29tcGFueSwgZm4gPSBmdW5jdGlvbih4KSBhcy5udW1lcmljKHggPT0gIk5VTEwiKSkgJT4lCiAgc3RlcF9tdXRhdGUoY291bnRyeSA9IGZjdF9sdW1wX24oY291bnRyeSwgNSkpICU+JQogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljKCkpICU+JQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLWFsbF9vdXRjb21lcygpKSAKYGBgCgpgYGB7cn0KaG90ZWxzX3JlY2lwZSAlPiUKICBwcmVwKGhvdGVsc190cmFpbmluZykgJT4lCiAganVpY2UoKQpgYGAKCjUuIEluIHRoaXMgc3RlcCB3ZSB3aWxsIHNldCB1cCBhIExBU1NPIG1vZGVsIGFuZCB3b3JrZmxvdy4KCiogSW4gZ2VuZXJhbCwgd2h5IHdvdWxkIHdlIHdhbnQgdG8gdXNlIExBU1NPIGluc3RlYWQgb2YgcmVndWxhciBsb2dpc3RpYyByZWdyZXNzaW9uPyAoSElOVDogdGhpbmsgYWJvdXQgd2hhdCBoYXBwZW5zIHRvIHRoZSBjb2VmZmljaWVudHMpLiAgCioqV2Ugd291bGQgY2hvb3NlIExBU1NPIG92ZXIgcmVndWxhciBsb2dpc3RpYyByZWdyZXNzaW9uIGJlY2F1c2Ugd2Ugd2lsbCBiZSBkb2luZyBtb2RlbCBzZWxlY3Rpb24gaW4gdGhlIHByb2Nlc3MuIExBU1NPIGlzIGEgc2hyaW5rYWdlIG1ldGhvZCB0aGF0IHNldHMgdGhlIGNvZWZmaWNpZW50IG9mIGluc2lnbmlmaWNhbnQgdmFyaWFibGVzIHRvIDAsIHRoZXJlYnkgZWxpbWluYXRpbmcgdGhvc2UgdmFyaWFibGVzIGZyb20gdGhlIHNldCBvZiBwcmVkaWN0b3JzLiBXaXRoIHJlZ3VsYXIgbG9naXN0aWMgcmVncmVzc2lvbiwgd2Ugd291bGQgaGF2ZSB0byBtYW51YWxseSB0ZXN0IGVhY2ggcHJlZGljdG9yIG9yIHRha2UgYSBzdGVwd2lzZSBhcHByb2FjaC4qKgoqIERlZmluZSB0aGUgbW9kZWwgdHlwZSwgc2V0IHRoZSBlbmdpbmUsIHNldCB0aGUgYHBlbmFsdHlgIGFyZ3VtZW50IHRvIGB0dW5lKClgIGFzIGEgcGxhY2Vob2xkZXIsIGFuZCBzZXQgdGhlIG1vZGUuICAKCmBgYHtyfQpob3RlbHNfbGFzc29fbW9kIDwtIAogIGxvZ2lzdGljX3JlZyhtaXh0dXJlID0gMSkgJT4lIAogIHNldF9lbmdpbmUoImdsbW5ldCIpICU+JSAKICBzZXRfYXJncyhwZW5hbHR5ID0gdHVuZSgpKSAlPiUgCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKYGBgCgoqIENyZWF0ZSBhIHdvcmtmbG93IHdpdGggdGhlIHJlY2lwZSBhbmQgbW9kZWwuICAKCmBgYHtyfQooaG90ZWxzX2xhc3NvX3dmIDwtIAogIHdvcmtmbG93KCkgJT4lCiAgYWRkX3JlY2lwZShob3RlbHNfcmVjaXBlKSAlPiUKICBhZGRfbW9kZWwoaG90ZWxzX2xhc3NvX21vZCkpCmBgYAoKCjYuIEluIHRoaXMgc3RlcCwgd2UnbGwgdHVuZSB0aGUgbW9kZWwgYW5kIGZpdCB0aGUgbW9kZWwgdXNpbmcgdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlciB0byB0aGUgZW50aXJlIHRyYWluaW5nIGRhdGFzZXQuCgoqIENyZWF0ZSBhIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uIHNhbXBsZS4gV2UnbGwgdXNlIHRoaXMgbGF0ZXIuIEkgaGF2ZSBzZXQgdGhlIHNlZWQgZm9yIHlvdS4gIAoKYGBge3J9CnNldC5zZWVkKDQ5NCkgIyBmb3IgcmVwcm9kdWNpYmlsaXR5Cgpob3RlbHNfY3YgPC0gdmZvbGRfY3YoaG90ZWxzX3RyYWluaW5nLCB2ID0gNSkKYGBgCgoqIFVzZSB0aGUgYGdyaWRfcmVndWxhcigpYCBmdW5jdGlvbiB0byBjcmVhdGUgYSBncmlkIG9mIDEwIHBvdGVudGlhbCBwZW5hbHR5IHBhcmFtZXRlcnMgKHdlJ3JlIGtlZXBpbmcgdGhpcyBzb3J0IG9mIHNtYWxsIGJlY2F1c2UgdGhlIGRhdGFzZXQgaXMgcHJldHR5IGxhcmdlKS4gVXNlIHRoYXQgd2l0aCB0aGUgNS1mb2xkIGN2IGRhdGEgdG8gdHVuZSB0aGUgbW9kZWwuICAKCmBgYHtyfQpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKHBlbmFsdHkoKSwgbGV2ZWxzID0gMTApCmBgYAoKKiBVc2UgdGhlIGB0dW5lX2dyaWQoKWAgZnVuY3Rpb24gdG8gZml0IHRoZSBtb2RlbHMgd2l0aCBkaWZmZXJlbnQgdHVuaW5nIHBhcmFtZXRlcnMgdG8gdGhlIGRpZmZlcmVudCBjcm9zcy12YWxpZGF0aW9uIHNldHMuICAKCmBgYHtyfQpob3RlbHNfbGFzc29fdHVuZSA8LSAKICBob3RlbHNfbGFzc29fd2YgJT4lIAogIHR1bmVfZ3JpZChyZXNhbXBsZXMgPSBob3RlbHNfY3YsIGdyaWQgPSBwZW5hbHR5X2dyaWQpCgpob3RlbHNfbGFzc29fdHVuZQpgYGAKCiogVXNlIHRoZSBgY29sbGVjdF9tZXRyaWNzKClgIGZ1bmN0aW9uIHRvIGNvbGxlY3QgYWxsIHRoZSBtZXRyaWNzIGZyb20gdGhlIHByZXZpb3VzIHN0ZXAgYW5kIGNyZWF0ZSBhIHBsb3Qgd2l0aCB0aGUgYWNjdXJhY3kgb24gdGhlIHktYXhpcyBhbmQgdGhlIHBlbmFsdHkgdGVybSBvbiB0aGUgeC1heGlzLiBQdXQgdGhlIHgtYXhpcyBvbiB0aGUgbG9nIHNjYWxlLiAgCgpgYGB7cn0KaG90ZWxzX2xhc3NvX3R1bmUgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpICU+JQogIGZpbHRlcigubWV0cmljID09ICJhY2N1cmFjeSIpICU+JQogIGdncGxvdChhZXMoeCA9IHBlbmFsdHksIHkgPSBtZWFuKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9saW5lKCkgKwogIHNjYWxlX3hfbG9nMTAoCiAgIGJyZWFrcyA9IHNjYWxlczo6dHJhbnNfYnJlYWtzKCJsb2cxMCIsIGZ1bmN0aW9uKHgpIDEwXngpLAogICBsYWJlbHMgPSBzY2FsZXM6OnRyYW5zX2Zvcm1hdCgibG9nMTAiLHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSkgKwogIGxhYnMoeCA9ICJwZW5hbHR5IiwgeSA9ICJhY2N1cmFjeSIpCmBgYAoKKiBVc2UgdGhlIGBzZWxlY3RfYmVzdCgpYCBmdW5jdGlvbiB0byBmaW5kIHRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXIsIGZpdCB0aGUgbW9kZWwgdXNpbmcgdGhhdCB0dW5pbmcgcGFyYW1ldGVyIHRvIHRoZSBlbnRpcmUgdHJhaW5pbmcgc2V0IChISU5UOiBgZmluYWxpemVfd29ya2Zsb3coKWAgYW5kIGBmaXQoKWApLCBhbmQgZGlzcGxheSB0aGUgbW9kZWwgcmVzdWx0cyB1c2luZyBgcHVsbF93b3JrZmxvd19maXQoKWAgYW5kIGB0aWR5KClgLiBBcmUgdGhlcmUgc29tZSB2YXJpYWJsZXMgd2l0aCBjb2VmZmljaWVudHMgb2YgMD8KCmBgYHtyfQooYmVzdF9wYXJhbSA8LSBob3RlbHNfbGFzc29fdHVuZSAlPiUgCiAgc2VsZWN0X2Jlc3QobWV0cmljID0gImFjY3VyYWN5IikpCmBgYAoKYGBge3J9Cihob3RlbHNfbGFzc29fZmluYWxfd2YgPC0gaG90ZWxzX2xhc3NvX3dmICU+JSAKICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3BhcmFtKSkKYGBgCgpgYGB7cn0KaG90ZWxzX2xhc3NvX2ZpbmFsX21vZCA8LSBob3RlbHNfbGFzc29fZmluYWxfd2YgJT4lIAogIGZpdChkYXRhID0gaG90ZWxzX3RyYWluaW5nKQoKaG90ZWxzX2xhc3NvX2ZpbmFsX21vZCAlPiUKICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JQogIHRpZHkoKQpgYGAKCmBgYHtyfQpob3RlbHNfbGFzc29fZmluYWxfbW9kICU+JQogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lCiAgdGlkeSgpICU+JQogIGZpbHRlcihlc3RpbWF0ZSA9PSAwKQpgYGAKCioqWWVzLCBmb3VyIHZhcmlhYmxlcyBoYWQgYSBjb2VmZmljaWVudCBzZXQgdG8gMC4qKgoKNy4gTm93IHRoYXQgd2UgaGF2ZSBhIG1vZGVsLCBsZXQncyBldmFsdWF0ZSBpdCBhIGJpdCBtb3JlLiBBbGwgd2UgaGF2ZSBsb29rZWQgYXQgc28gZmFyIGlzIHRoZSBjcm9zcy12YWxpZGF0ZWQgYWNjdXJhY3kgZnJvbSB0aGUgcHJldmlvdXMgc3RlcC4gCgoqIENyZWF0ZSBhIHZhcmlhYmxlIGltcG9ydGFuY2UgZ3JhcGguIFdoaWNoIHZhcmlhYmxlcyBzaG93IHVwIGFzIHRoZSBtb3N0IGltcG9ydGFudD8gQXJlIHlvdSBzdXJwcmlzZWQ/ICAKCmBgYHtyfQpob3RlbHNfbGFzc29fZmluYWxfbW9kICU+JQogIHB1bGxfd29ya2Zsb3dfZml0KCkgJT4lCiAgdmlwKCkKYGBgCgoqKkl0IGlzIGludGVyZXN0aW5nIHRvIHNlZSB3aGF0IG5vbi1yZWZ1bmRhYmxlIGJvb2tpbmdzIHdlcmUgbW9zdCBsaWtlbHkgdG8gZ2V0IGNhbmNlbGVkLCB3aGVuIGNvbXBhcmVkIHRvIHRob3NlIHRoYXQgd2VyZSByZWZ1bmRhYmxlIGFuZCBkaWRuJ3QgcmVxdWlyZSBhIGRlcG9zaXQuIEkgd291bGQndmUgdGhvdWdodCB0aGF0IHBlb3BsZSB3ZXJlIG1vcmUgbGlrZWx5IHRvIGtlZXAgdGhlaXIgYm9va2luZ3MgaWYgY2FuY2VsaW5nIG1lYW50IHRoZXkgd291bGQgbG9zZSB0aGVpciBkZXBvc2l0cy4gRGVmaW5pdGVseSBzdXJwcmlzZWQgYnkgaG93IHRoZSBtb3JlIGltcG9ydGFudCB2YXJpYWJsZXMgYXJlIGNhdGVnb3JpZXMgb2YgdGhlIGFzc2lnbmVkIGFuZCByZXNlcnZlZCByb29tIHR5cGVzLCBjb25zaWRlcmluZyB0aGVzZSBoYWQgc28gbWFueSBjYXRlZ29yaWVzIGVhY2ggYW5kIEkgaW5pdGlhbGx5IHRob3VnaHQgdGhleSB3ZXJlIHF1aXRlIGFyYml0cmFyeS4gSXQgZG9lcyBtYWtlIHNlbnNlIHRoYXQgcGVvcGxlIG1pZ2h0IGNhbmNlbCBiZWNhdXNlIHRoZXkgd2VyZSB1bnNhdGlzZmllZCB3aXRoIHRoZSByb29tIHRoZXkgd2VyZSBhc3NpZ25lZCB3aXRoLiBGb3IgZnV0dXJlIHdvcmssIGl0IG1pZ2h0IGJlIHdvcnRod2hpbGUgdG8gY3JlYXRlIGFuIGluZGljYXRvciB2YXJpYWJsZSBmb3Igd2hldGhlciB0aGUgYXNzaWduZWQgcm9vbSB0eXBlIG1hdGNoZWQgdGhlIHJlc2VydmVkLiBIZXJlLCBJIHF1aWNrbHkgaW52ZXN0aWdhdGVkIGhvdyBtYW55IGJvb2tpbmdzIGRpZCBhbmQgZGlkIG5vdCByZWNlaXZlIHRoZSByb29tIHR5cGUgdGhleSByZXNlcnZlZDoqKgoKYGBge3J9CmhvdGVscyAlPiUKICBtdXRhdGUocmVjZWl2ZWRfcmVzZXJ2ZWRfcm9vbV90eXBlID0gcmVzZXJ2ZWRfcm9vbV90eXBlID09IGFzc2lnbmVkX3Jvb21fdHlwZSkgJT4lCiAgY291bnQocmVjZWl2ZWRfcmVzZXJ2ZWRfcm9vbV90eXBlLCBuYW1lID0gIm4iKSAlPiUKICBtdXRhdGUocCA9IG4vbnJvdyhob3RlbHMpKQpgYGAKCiogVXNlIHRoZSBgbGFzdF9maXQoKWAgZnVuY3Rpb24gdG8gZml0IHRoZSBmaW5hbCBtb2RlbCBhbmQgdGhlbiBhcHBseSBpdCB0byB0aGUgdGVzdGluZyBkYXRhLiBSZXBvcnQgdGhlIG1ldHJpY3MgZnJvbSB0aGUgdGVzdGluZyBkYXRhIHVzaW5nIHRoZSBgY29sbGV0X21ldHJpY3MoKWAgZnVuY3Rpb24uIEhvdyBkbyB0aGV5IGNvbXBhcmUgdG8gdGhlIGNyb3NzLXZhbGlkYXRlZCBtZXRyaWNzPwoKYGBge3J9CmhvdGVsc19sYXNzb190ZXN0IDwtIGhvdGVsc19sYXNzb19maW5hbF93ZiAlPiUKICBsYXN0X2ZpdChob3RlbHNfc3BsaXQpCmBgYAoKYGBge3J9CmhvdGVsc19sYXNzb190ZXN0ICU+JQogIGNvbGxlY3RfbWV0cmljcygpCmBgYAoKYGBge3J9CmhvdGVsc19sYXNzb190dW5lICU+JQogIGNvbGxlY3RfbWV0cmljcygpICU+JQogIGZpbHRlcihwZW5hbHR5ID09IGJlc3RfcGFyYW0kcGVuYWx0eSkKYGBgCgoqKlRoZSBtZXRyaWNzIGZyb20gdGhlIHRlc3RpbmcgZGF0YSBhcmUgc2xpZ2h0bHkgaGlnaGVyIHRoYW4gdGhlIGNyb3NzLXZhbGlkYXRlZCBtZXRyaWNzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGJlc3QgcGVuYWx0eSB2YWx1ZS4qKgoKKiBVc2UgdGhlIGBjb2xsZWN0X3ByZWRpY3Rpb25zKClgIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGFuZCBjbGFzc2VzIGZvciB0aGUgdGVzdCBkYXRhLiBTYXZlIHRoaXMgdG8gYSBuZXcgZGF0YXNldCBjYWxsZWQgYHByZWRzYC4gVGhlbiwgdXNlIHRoZSBgY29uZl9tYXQoKWAgZnVuY3Rpb24gZnJvbSBgZGlhbHNgIChwYXJ0IG9mIGB0aWR5bW9kZWxzYCkgdG8gY3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeCBzaG93aW5nIHRoZSBwcmVkaWN0ZWQgY2xhc3NlcyB2cy4gdGhlIHRydWUgY2xhc3Nlcy4gV2hhdCBpcyB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIChzZW5zaXRpdml0eSk/IFdoYXQgaXMgdGhlIHRydWUgbmVnYXRpdmUgcmF0ZSAoc3BlY2lmaWNpdHkpPyBTZWUgdGhpcyBbV2lraXBlZGlhXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Db25mdXNpb25fbWF0cml4KSByZWZlcmVuY2UgaWYgeW91IChsaWtlIG1lKSB0ZW5kIHRvIGZvcmdldCB0aGVzZSBkZWZpbml0aW9ucy4KCmBgYHtyfQpwcmVkcyA8LSBjb2xsZWN0X3ByZWRpY3Rpb25zKGhvdGVsc19sYXNzb190ZXN0KQpjb25mX21hdChwcmVkcywgdHJ1dGggPSBpc19jYW5jZWxlZCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKYGBgCgoqICoqU2Vuc2l0aXZpdHkgPSAxNDM0Ni8oMTQzNDYgKyA3NzgyKSA9IDAuNjQ4MzE4OSoqCiogKipTcGVjaWZpY2l0eSA9IDM0MjI1LygzNDIyNSArIDMzNDApID0gMC45MTEwODc0KioKCiogVXNlIHRoZSBgcHJlZHNgIGRhdGFzZXQgeW91IGp1c3QgY3JlYXRlZCB0byBjcmVhdGUgYSBkZW5zaXR5IHBsb3Qgb2YgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIG9mIGNhbmNlbGluZyAodGhlIHZhcmlhYmxlIGlzIGNhbGxlZCBgLnByZWRfMWApLCBmaWxsaW5nIGJ5IGBpc19jYW5jZWxlZGAuIFVzZSBhbiBgYWxwaGEgPSAuNWAgYW5kIGBjb2xvciA9IE5BYCBpbiB0aGUgYGdlb21fZGVuc2l0eSgpYC4gQW5zd2VyIHRoZXNlIHF1ZXN0aW9uczogYS4gV2hhdCB3b3VsZCB0aGlzIGdyYXBoIGxvb2sgbGlrZSBmb3IgYSBtb2RlbCB3aXRoIGFuIGFjY3VyYWN5IHRoYXQgd2FzIGNsb3NlIHRvIDE/IGIuIE91ciBwcmVkaWN0aW9ucyBhcmUgY2xhc3NpZmllZCBhcyBjYW5jZWxlZCBpZiB0aGVpciBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgY2FuY2VsaW5nIGlzIGdyZWF0ZXIgdGhhbiAuNS4gSWYgd2Ugd2FudGVkIHRvIGhhdmUgYSBoaWdoIHRydWUgcG9zaXRpdmUgcmF0ZSwgc2hvdWxkIHdlIG1ha2UgdGhlIGN1dG9mZiBmb3IgcHJlZGljdGVkIGFzIGNhbmNlbGVkIGhpZ2hlciBvciBsb3dlciB0aGFuIC41PyBjLiBXaGF0IGhhcHBlbnMgdG8gdGhlIHRydWUgbmVnYXRpdmUgcmF0ZSBpZiB3ZSB0cnkgdG8gZ2V0IGEgaGlnaGVyIHRydWUgcG9zaXRpdmUgcmF0ZT8gCgpgYGB7cn0KZ2dwbG90KHByZWRzLCBhZXMoeCA9IC5wcmVkXzEsIGZpbGwgPSBpc19jYW5jZWxlZCkpICsKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAuNSwgY29sb3IgPSBOQSkgKwogIGxhYnMoeCA9ICJQcmVkaWN0ZWQgcHJvYmFiaWxpdHkiLCB5ID0gIkRlbnNpdHkiLCBmaWxsID0gIklzIGNhbmNlbGVkIikKYGBgCgoqICoqUHJlZGljdGlvbnMgZnJvbSBhIG1vZGVsIHdpdGggYW4gYWNjdXJhY3kgdGhhdCB3YXMgY2xvc2UgdG8gMSB3b3VsZCBiZSBtb3JlIGFjY3VyYXRlLiBGb3IgYm9va2luZ3MgdGhhdCB3ZXJlIGFjdHVhbGx5IGNhbmNlbGVkLCB0aGUgcHJlZGljdGlvbnMgd291bGQgYmUgY2xvc2VyIHRvIDEgYW5kIHRoZXJlIHdvdWxkIGJlIG1vcmUgZGVuc2l0eSBjb25jZW50cmF0ZWQgaW4gdGhlIHJpZ2h0bW9zdCBwYXJ0IG9mIHRoZSBncmFwaC4gTGlrZXdpc2UsIGZvciBib29raW5ncyB0aGF0IHdlcmVuJ3QgY2FuY2VsZWQsIHRoZWlyIHByZWRpY3Rpb25zIHdvdWxkIGJlIGNsb3NlciB0byAwIGFuZCBtb3N0IG9mIHRoZSBkZW5zaXR5IHdvdWxkIGJlIGluIHRoZSBsZWZ0bW9zdCBwYXJ0LiAqKgoqICoqSWYgd2Ugd2FudGVkIHRvIGluY3JlYXNlIHNlbnNpdGl2aXR5LCB3ZSBzaG91bGQgbWFrZSB0aGUgY3V0b2ZmIGxlc3MgdGhhbiAuNS4qKgoqICoqVGhlIHRydWUgbmVnYXRpdmUgcmF0ZSB3b3VsZCBkZWNyZWFzZSBpZiB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIGluY3JlYXNlcy4qKgoKOC4gTGV0J3Mgc2F5IHRoYXQgdGhpcyBtb2RlbCBpcyBnb2luZyB0byBiZSBhcHBsaWVkIHRvIGJvb2tpbmdzIDE0IGRheXMgaW4gYWR2YW5jZSBvZiB0aGVpciBhcnJpdmFsIGF0IGVhY2ggaG90ZWwsIGFuZCBzb21lb25lIHdobyB3b3JrcyBmb3IgdGhlIGhvdGVsIHdpbGwgbWFrZSBhIHBob25lIGNhbGwgdG8gdGhlIHBlcnNvbiB3aG8gbWFkZSB0aGUgYm9va2luZy4gRHVyaW5nIHRoaXMgcGhvbmUgY2FsbCwgdGhleSB3aWxsIHRyeSB0byBhc3N1cmUgdGhhdCB0aGUgcGVyc29uIHdpbGwgYmUga2VlcGluZyB0aGVpciByZXNlcnZhdGlvbiBvciB0aGF0IHRoZXkgd2lsbCBiZSBjYW5jZWxpbmcgaW4gd2hpY2ggY2FzZSB0aGV5IGNhbiBkbyB0aGF0IG5vdyBhbmQgc3RpbGwgaGF2ZSB0aW1lIHRvIGZpbGwgdGhlIHJvb20uIEhvdyBzaG91bGQgdGhlIGhvdGVsIGdvIGFib3V0IGRlY2lkaW5nIHdobyB0byBjYWxsPyBIb3cgY291bGQgdGhleSBtZWFzdXJlIHdoZXRoZXIgaXQgd2FzIHdvcnRoIHRoZSBlZmZvcnQgdG8gZG8gdGhlIGNhbGxpbmc/IENhbiB5b3UgdGhpbmsgb2YgYW5vdGhlciB3YXkgdGhleSBtaWdodCB1c2UgdGhlIG1vZGVsPyAKCioqVG8gc2F2ZSB0aW1lLCB0aGUgaG90ZWwgY2FuIHJ1biB0aGUgYm9va2luZ3MgdGhyb3VnaCB0aGUgbW9kZWwgdG8gZmluZCB0aGVpciBwcm9iYWJpbGl0eSBvZiBiZWluZyBjYW5jZWxlZCBhbmQgcHJpb3JpdGl6ZSBjYWxsaW5nIHBlb3BsZSB3aG8gbWFkZSB0aGUgYm9va2luZ3Mgd2l0aCB0aGUgaGlnaGVzdCBwcm9iYWJpbGl0aWVzLiBUaGV5IGNhbiBhbHNvIGZpbmQgYm9va2luZ3Mgd2l0aCBhdHRyaWJ1dGVzIGNvbnNpZGVyZWQgaW1wb3J0YW50IGJ5IHRoZSBtb2RlbCAoZS5nLiByZXNlcnZpbmcgcm9vbSB0eXBlIFAsIGJlaW5nIG5vbi1yZWZ1bmRhYmxlLCBldGMuKSBhbmQgdmVyaWZ5IHRob3NlIGZpcnN0LiBJZiB0aG9zZSBib29raW5ncyBhcmUgY29uZmlybWVkIHRvIGJlIGNhbmNlbGVkIHRoZW4gaXQgd2FzIHdvcnRoIHRoZSBlZmZvcnQgc2luY2UgdGhlIGhvdGVsIGNhbiB0aGVuIGltbWVkaWF0ZWx5IG9wZW4gdXAgdGhlIHJvb20gZm9yIHJlc2VydmF0aW9uLiBJbiBnZW5lcmFsLCB0aGV5IGNhbiBjb21wYXJlIHRoZSBmaW5hbCBzdGF0dXMgb2YgdGhlIGJvb2tpbmdzIChjYW5jZWxlZCB2cy4gbm90IGNhbmNlbGVkKSB0byB0aGUgb3V0Y29tZSBwcmVkaWN0ZWQgYnkgdGhlIG1vZGVsIHRvIGV2YWx1YXRlLioqCgo5LiBIb3cgbWlnaHQgeW91IGdvIGFib3V0IHF1ZXN0aW9uaW5nIGFuZCBldmFsdWF0aW5nIHRoZSBtb2RlbCBpbiB0ZXJtcyBvZiBmYWlybmVzcz8gQXJlIHRoZXJlIGFueSBxdWVzdGlvbnMgeW91IHdvdWxkIGxpa2UgdG8gYXNrIG9mIHRoZSBwZW9wbGUgd2hvIGNvbGxlY3RlZCB0aGUgZGF0YT8gCgoqKkkgdGhpbmsgdGhlIGZpcnN0IHN0ZXAgaXMgdG8gZXhhbWluZSB0aGUgZGF0YSBhbmQgc2VlIHdoZXRoZXIgaXQgaXMgcmVwcmVzZW50YXRpdmUgb2YgbW9zdCBib29raW5ncyBpZiB3ZSBhcmUgdG8gdXNlIHRoZSBtb2RlbCBvbiBib29raW5ncyBtYWRlIGJ5IG90aGVyIGhvdGVscyBub3QgaW4gdGhlIGRhdGFzZXQuIFdlIGNhbiB0ZXN0IHRoZSBtb2RlbCBvbiBuZXcgZGF0YSAoaWYgYXZhaWxhYmxlKSBhbmQgcmUtZXZhbHVhdGUgaXRzIG1ldHJpY3MgdG8gZW5zdXJlIHRoYXQgaXQgaXMgc3RpbGwgcHJvZHVjaW5nIGFjY3VyYXRlIHJlc3VsdHMuIFNpbmNlIHRoZSBkYXRhIGNhbWUgZnJvbSB0d28gZGlmZmVyZW50IGhvdGVscywgd2UgbWlnaHQgd2FudCB0byBhc2sgYWJvdXQgdGhlIGRlbW9ncmFwaGljcyB0aGF0IG1vc3QgY29tbW9ubHkgc3RheSBhdCBlYWNoIHBsYWNlLiBGb3IgZXhhbXBsZSwgd2UgaGF2ZSBhIGhvdGVsIHRoYXQgaXMgbG9jYXRlZCBpbiB0aGUgY2l0eSBhbmQgb25lIHRoYXQncyBhIGJlYWNoIHJlc29ydCwgc28gdGhlIGZvcm1lciBtaWdodCBiZSBtb3JlIHBvcHVsYXIgYW1vbmcgcGVvcGxlIG9uIGJ1c2luZXNzIHRyaXBzIHdoaWxlIHRoZSBsYXR0ZXIgYW1vbmcgdGhvc2Ugb24gdmFjYXRpb24uIEZ1cnRoZXJtb3JlLCBvbmUgbWlnaHQgYmUgbW9yZSBwb3B1bGFyIHdpdGggaW50ZXJuYXRpb25hbCB0cmF2ZWxlcnMgd2hpbGUgdGhlIG90aGVyIHdpdGggZG9tZXN0aWMuIE9uZSBncm91cCBtaWdodCBiZSBtb3JlIG9yIGxlc3MgbGlrZWx5IHRvIGNhbmNlbCBzbyBpdCBtaWdodCBiZSB3b3J0aHdoaWxlIHRvIGNoZWNrIGJlZm9yZSB3ZSBnZW5lcmFsaXplIHRoZSBtb2RlbCB0byBhIGhldGVyb2dlbmVvdXMgcG9wdWxhdGlvbi4qKgoKIyMgQmlhcyBhbmQgRmFpcm5lc3MKCkxpc3RlbiB0byBEci4gUmFjaGVsIFRob21hcydzICBbQmlhcyBhbmQgRmFpcm5lc3MgbGVjdHVyZV0oaHR0cHM6Ly9ldGhpY3MuZmFzdC5haS92aWRlb3MvP2xlc3Nvbj0yKS4gV3JpdGUgYSBicmllZiBwYXJhZ3JhcGggcmVmbGVjdGluZyBvbiBpdC4gWW91IG1pZ2h0IGFsc28gYmUgaW50ZXJlc3RlZCBpbiByZWFkaW5nIHRoZSBbUHJvUHVibGljYSBhcnRpY2xlXShodHRwczovL3d3dy5wcm9wdWJsaWNhLm9yZy9hcnRpY2xlL21hY2hpbmUtYmlhcy1yaXNrLWFzc2Vzc21lbnRzLWluLWNyaW1pbmFsLXNlbnRlbmNpbmcpIERyLiBUaG9tYXMgcmVmZXJlbmNlcyBhYm91dCB1c2luZyBhIHRvb2wgY2FsbGVkIENPTVBBUyB0byBwcmVkaWN0IHJlY2lkaXZpc20uIFNvbWUgcXVlc3Rpb25zL2lkZWFzIHlvdSBtaWdodCBrZWVwIGluIG1pbmQ6CgoqIERpZCB5b3UgaGVhciBhbnl0aGluZyB0aGF0IHN1cnByaXNlZCB5b3U/ICAKKiBXaHkgaXMgaXQgaW1wb3J0YW50IHRoYXQgd2UgcGF5IGF0dGVudGlvbiB0byBiaWFzIGFuZCBmYWlybmVzcyB3aGVuIHN0dWR5aW5nIGRhdGEgc2NpZW5jZT8gIAoqIElzIHRoZXJlIGEgdHlwZSBvZiBiaWFzIERyLiBUaG9tYXMgZGlzY3Vzc2VkIHRoYXQgd2FzIG5ldyB0byB5b3U/IENhbiB5b3UgdGhpbmsgYWJvdXQgcGxhY2VzIHlvdSBoYXZlIHNlZW4gdGhlc2UgdHlwZXMgb2YgYmlhc2VzPwoKKipXZSBuZWVkIHRvIGJlIG1pbmRmdWwgb2YgYmlhcyBhbmQgZmFpcm5lc3MgaW4gZGF0YSBzY2llbmNlIGJlY2F1c2UgbGlrZSBEci4gVGhvbWFzIHNhaWQsIGl0cyBhcHBsaWNhdGlvbnMgYXJlIGJlY29taW5nIGluY3JlYXNpbmdseSBwcmV2YWxlbnQgYW5kIHRoZXJlZm9yZSBpdHMgaW1wYWN0IGNhbiBiZSBhbXBsaWZpZWQgb24gYSBsYXJnZSBzY2FsZS4gU2luY2UgYWxnb3JpdGhtcyByZXF1aXJlIHRyYWluaW5nIGRhdGEsIHdlIGNhbiBhcnJpdmUgYXQgYSB2aWNpb3VzIGZlZWRiYWNrIGN5Y2xlIHdoZXJlIGFuIGFsZ29yaXRobSB0cmFpbmVkIG9uIGJpYXNlZCBkYXRhIHdpbGwgcHJvZHVjZSBiaWFzZWQgcmVzdWx0cywgd2hpY2ggd291bGQgdGhlbiBiZSBmZWQgYmFjayBpbnRvIHRoZSBhbGdvcml0aG0gYXMgcGFydCBvZiB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgcGVycGV0dWF0ZSB0aGVzZSBiaWFzZXMgb24gYW4gZXZlbiBicm9hZGVyIHNjYWxlLiBXZSBuZWVkIHRvIHVuZGVyc3RhbmQgYW5kIGJlIG1pbmRmdWwgb2YgYmlhcyBhbmQgZmFpcm5lc3Mgd2hlbiBzdHVkeWluZyBkYXRhIHNjaWVuY2UgYmVjYXVzZSB0aGVuIHdlJ2Qga25vdyB0aGF0IHRoZXNlIGFyZSB0aGUgdGhpbmdzIHdlIHNob3VsZCBiZSBsb29raW5nIG91dCBmb3IgYW5kIG1ha2UgdGhlbSBhbiBlc3NlbnRpYWwgcGFydCBvZiBvdXIgZGF0YSBhbmFseXNlcyAvIHRvb2wgYnVpbGRpbmcuIElmIHRoZSBwZW9wbGUgY3JlYXRpbmcgdGhlIHRvb2xzIGFuZCBhbGdvcml0aG1zIGFyZW4ndCBhd2FyZSBvZiB0aGUgaXNzdWVzIHRoZW1zZWx2ZXMgdGhlbiBpdCB3b3VsZCBiZSBkaWZmaWN1bHQgZm9yIHRoZSBwdWJsaWMgdG8gcmVjb2duaXplIGFuZCBldmFsdWF0ZSB0aGVtLiBJJ3ZlIGhlYXJkIG9mIGFsbCB0aGUgYmlhc2VzIHRoYXQgRHIuIFRob21hcyBpbnRyb2R1Y2VkIGJ1dCBhbSBub3Qgc3VwZXIgZmFtaWxpYXIgd2l0aCB0aGVtIGFsbC4gT25lIHRoYXQgSSBmb3VuZCBpbnRlcmVzdGluZyB3YXMgYWdncmVnYXRpb24gYmlhcywgd2hpY2ggYmFzZWQgb24gbXkgdW5kZXJzdGFuZGluZyBpcyB0aGUgYmlhcyB0aGF0IGFyaXNlcyBmcm9tIHVzaW5nIGEgdW5pdmVyc2FsIG1vZGVsIGZvciBncm91cHMgd2l0aCBkaWZmZXJlbnQgY2hhcmFjdGVyaXN0aWNzLiBJIGFzc3VtZSB0aGF0IGlmIG9uZSBtb2RlbCB3YXMgdGVzdGVkIHRvIGJlIGluZWZmaWNpZW50IGZvciBhIGRpZmZlcmVudCBncm91cCB0aGVuIGl0IHNob3VsZG4ndCBiZSByZWxlYXNlZCBpbiB0aGUgZmlyc3QgcGxhY2U/IEJ1dCB0aGVuIGFnYWluIHRoYXQncyBhc3N1bWluZyB0aGUgZGV2ZWxvcGVycyB0aG9yb3VnaGx5IGV2YWx1YXRlZCBpdC4gTGFzdCBzZW1lc3RlciBJIHRvb2sgYSBjb3VwbGUgb2YgY291cnNlcyB0aGF0IGFsc28gaGFkIG1lIHRoaW5raW5nIGFib3V0IGFsZ29yaXRobWljIGJpYXMgYW5kIHRoZSBtb3N0IGNvbW1vbiBleGFtcGxlcyBJIGNhbiB0aGluayBvZiBhcmUgaW4gaGVhbHRoY2FyZSBhbmQgY3JpbWluYWwganVzdGljZS4qKgoKCg==